package edu.cs.usask.ca.silver.plugin.visualization;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;

import java.util.Iterator;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JToolTip;

import bubblesets.layout.BubbleSetLayout;
import bubblesets.lib.AggregatesLib;
import bubblesets.lib.Colors;

import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.Action;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.activity.Activity;
import prefuse.controls.PanControl;
import prefuse.controls.ToolTipControl;
import prefuse.controls.ZoomControl;
import prefuse.data.Graph;
import prefuse.data.io.DataIOException;
import prefuse.data.io.GraphMLReader;
import prefuse.data.util.Sort;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.render.PolygonRenderer;
import prefuse.render.Renderer;
import prefuse.render.ShapeRenderer;
import prefuse.util.ColorLib;
import prefuse.visual.AggregateItem;
import prefuse.visual.AggregateTable;
import prefuse.visual.VisualGraph;
import prefuse.visual.VisualItem;

@SuppressWarnings("serial")
public class VisualWorkspace extends Display {

	private static final String NEWLINE = System.getProperty("line.separator");

	public static final String GRAPH = "graph";
	public static final String NODES = "graph.nodes";
	public static final String EDGES = "graph.edges";
	public static final String AGGR = "aggregates";
	public static final String GROUP = "group";
	public static final String BUB_NODES = "data";
	public static final String REPAINT = "repaint";
	public static final String CLUSTERFIELD = "cluster"; // int
	public static final String SWITCH_QUALITY = "switch_quality";
	public static final String FAKE_PARAMETER_SET = "Last Updated: " + NEWLINE
			+ " 2010 Feb 16th 17:13pm" + NEWLINE + NEWLINE + "Parameter Sets: "
			+ NEWLINE + " population = 1000 " + NEWLINE + " x = 1 " + NEWLINE
			+ " fraction of a = 0.19 " + NEWLINE + " Author: " + NEWLINE
			+ " Yudi Xue ";
	public static final String CLUSTER = BubbleSetLayout.AGGREGATE_NUMBER;

	private BubbleSetLayout aggregateLayout;

	private boolean high_quality = false;

	public VisualWorkspace() {

		// initialize display and data
		super(new Visualization());
		initDataGroups();

		// set up the renderers
		// draw the nodes as basic shapes
		Renderer nodeR = new ShapeRenderer(5);
		LabelRenderer r = new LabelRenderer("type");
		r.setHorizontalPadding(15);
		r.setVerticalPadding(10);
		r.setRoundedCorner(50, 50); // round the corners

		// draw aggregates as polygons with curved edges
		PolygonRenderer polyR = new PolygonRenderer(Constants.POLY_TYPE_CURVE);
		polyR.setCurveSlack(0.15f);
		polyR.setClosePath(true);

		DefaultRendererFactory drf = new DefaultRendererFactory();

		drf.setDefaultRenderer(nodeR);
		drf.setDefaultRenderer(r);

		drf.add("ingroup('aggregates')", polyR);
		m_vis.setRendererFactory(drf);

		// set up the visual operators
		// first set up all the color actions
		ColorAction nStroke = new ColorAction(NODES, VisualItem.STROKECOLOR);
		nStroke.setDefaultColor(ColorLib.gray(100));
		nStroke.add("_hover", ColorLib.gray(50));

		int[] node_palette = new int[] { ColorLib.hex("D0E8A1"),
				// ColorLib.hex("99E89D"),
				ColorLib.rgb(190, 190, 255), ColorLib.hex("6DD19D"),
				ColorLib.hex("D40049") };
		// map nominal data values to colors using our provided palette
		DataColorAction nfill = new DataColorAction(NODES, "type",
				Constants.NOMINAL, VisualItem.FILLCOLOR, node_palette);

		// ColorAction nFill = new ColorAction(NODES, VisualItem.FILLCOLOR);
		// nFill.setDefaultColor(ColorLib.gray(255));
		// nFill.add("_hover", ColorLib.gray(200));
		ColorAction text = new ColorAction(NODES, VisualItem.TEXTCOLOR,
				ColorLib.gray(0));

		ColorAction nEdges = new ColorAction(EDGES, VisualItem.STROKECOLOR);
		nEdges.setDefaultColor(ColorLib.gray(100));

		// create bubbleset colors
		// int[] palette = new int[] { ColorLib.rgba(255, 200, 200, 150),
		// ColorLib.rgba(200, 255, 200, 150),
		// ColorLib.rgba(200, 200, 255, 150) };
		Colors c = new Colors();
		int[] palette = c.getPalette("ANALOGOUS_PALETTE_WARM");
		int[] coolpalette = c.getPalette("HUE_PALETTE");

		ColorAction aFill = new DataColorAction(AGGR, CLUSTER,
				Constants.NOMINAL, VisualItem.FILLCOLOR, getTransparentPalette(
						coolpalette, 40));

//		aFill.add(
//				new InGroupPredicate(Visualization.FOCUS_ITEMS),
//				new DataColorAction(AGGR, CLUSTER, Constants.NOMINAL,
//						VisualItem.FILLCOLOR, getTransparentPalette(
//								coolpalette, 70)));
		aFill.add(
				"_hover",
				new DataColorAction(AGGR, CLUSTER, Constants.NOMINAL,
						VisualItem.FILLCOLOR, getTransparentPalette(
								coolpalette, 90)));

		// ColorAction aStroke = new ColorAction(AGGR, VisualItem.STROKECOLOR);
		// aStroke.setDefaultColor(ColorLib.gray(200));
		// aStroke.add("_hover", ColorLib.rgb(255, 100, 100));

		ColorAction aStroke = new DataColorAction(AGGR, CLUSTER,
				Constants.NOMINAL, VisualItem.STROKECOLOR,
				getTransparentPalette(coolpalette, 20));
//		aStroke.add(
//				new InGroupPredicate(Visualization.FOCUS_ITEMS),
//				new DataColorAction(AGGR, CLUSTER, Constants.NOMINAL,
//						VisualItem.STROKECOLOR, getTransparentPalette(
//								coolpalette, 110)));
		aStroke.add("_hover", new DataColorAction(AGGR, CLUSTER,
				Constants.NOMINAL, VisualItem.STROKECOLOR,
				getTransparentPalette(coolpalette, 120)));

		m_vis.putAction(REPAINT, new RepaintAction());

		// Visualization quality action
		Action switchQuality = new Action() {
			public void run(double frac) {
				high_quality = !high_quality;
				setHighQuality(high_quality);
				m_vis.run("layout");
				m_vis.run(REPAINT);
			}
		};

		m_vis.putAction(SWITCH_QUALITY, switchQuality);

		// set up bubble set
		aggregateLayout = new BubbleSetLayout(AGGR, NODES, NODES, 14, 28, 8,
				16, 0.8, 2, -3, 0, true, false, true);
		// NODES should be a group to tupleset
		// aggregateLayout.setSmoothing(1);
		// aggregateLayout.setSkip(1);
		// aggregateLayout.setMorphBuffer(5);

		aggregateLayout.setSmoothing(1);
		aggregateLayout.setSkip(1);
		aggregateLayout.setMorphBuffer(5);

		// high quality
		// aggregateLayout.setPixelGroup(4);
		// aggregateLayout.setSkip(5);//10); // skip not necessary when using
		// smoothing
		// aggregateLayout.setNodeInfluence(0.8, -2);
		// aggregateLayout.setSmoothing(1);
		// aggregateLayout.setDoMorph(true);
		// aggregateLayout.considerBlockingItems = true;
		// aggregateLayout.doMerge = true;

		// bundle the color actions
		ActionList colors = new ActionList();
		colors.add(nStroke);
		colors.add(text);
		colors.add(nfill);
		colors.add(nEdges);
		colors.add(aStroke);
		colors.add(aFill);

		// now create the main layout routine
		ActionList layout = new ActionList(Activity.INFINITY);
		layout.add(colors);
		layout.add(new ForceDirectedLayout(GRAPH, true));
		layout.add(aggregateLayout);

		JTable a;

		// layout.add(new BubbleSetLayout(AGGR, NODES));

		layout.add(new RepaintAction());
		m_vis.putAction("layout", layout);

		// set up the display
		setSize(500, 500);
		pan(250, 250);
		setHighQuality(high_quality);
		addControlListener(new AggregateDragControl());
		addControlListener(new ZoomControl());
		addControlListener(new PanControl());

		setCustomToolTip(new CustomTooltip());
		ToolTipControl ttc = new ToolTipControl(new String[] {
		// one of 4 columns:
		// type
		// name
		// cluster
		// gender
				"type" }) {

			@Override
			public void itemEntered(VisualItem item, MouseEvent e) {

				if (item.isInGroup(AGGR)) {
					setToolTipText(FAKE_PARAMETER_SET);
				} else {
					super.itemEntered(item, e);
				}
			}
		};
		addControlListener(ttc);

		// in integration, use a command and SWT key listener
		addKeyListener(new KeyAdapter() {
			@Override
			public void keyReleased(KeyEvent arg0) {
				if (arg0.getKeyCode() == KeyEvent.VK_Q) {
					m_vis.run(SWITCH_QUALITY);
					return;
				}
			}
		});

		// set things running
		m_vis.run("layout");

	}

	private void initDataGroups() {
		// create sample graph
		// 9 nodes broken up into 3 interconnected cliques
		Graph g = null;
		try {
			g = new GraphMLReader().readGraph("/Users/yudi/development/workspaces/workspace6-rcp/edu.cs.usask.ca.silver.fragment.visualization.data/src/silver.xml");
		} catch (DataIOException e) {
			e.printStackTrace();
			System.err.println("Error loading graph. Exiting...");
			System.exit(1);
		}

		// add visual data groups
		VisualGraph vg = m_vis.addGraph(GRAPH, g);

		m_vis.setInteractive(EDGES, null, false);
		m_vis.setValue(NODES, null, VisualItem.SHAPE, new Integer(
				Constants.SHAPE_ELLIPSE));

		// Table t = g.getNodeTable();

		// Table t has:
		// 4 columns:
		// type
		// name
		// cluster
		// gender

		// m_vis.add(BUB_NODES, t);
		// t.addColumn(CLUSTER, int[].class, new int[0]);
		vg.addColumn(CLUSTER, int[].class, new int[0]);

		final AggregateTable at = m_vis.addAggregates(AGGR);
		at.addColumns(BubbleSetLayout.SCHEMA);

		// add nodes to aggregates
		// create an aggregate for each 3-clique of nodes
	
		Iterator<?> items = m_vis.getVisualGroup(NODES).tuples(null,
				Sort.parse(CLUSTERFIELD + " ASC"));
		AggregateItem aItem = null;
		int cluster;
		while (items.hasNext()) {
			VisualItem v = (VisualItem) items.next();
			cluster = v.getInt(CLUSTERFIELD);
			if (cluster > 0) {
				AggregatesLib.clusterExists(at, cluster, CLUSTER);
				aItem = AggregatesLib.getAItem(at, cluster, CLUSTER);
				aItem.addItem(v);
			}
		}
		m_vis.setInteractive(AGGR, null, true);
	}

	@Override
	public void setHighQuality(boolean highQuality) {
		super.setHighQuality(highQuality);
		if (highQuality) {
			// high quality
			aggregateLayout.setPixelGroup(4);
			aggregateLayout.setSkip(5);// 10); // skip not necessary when using
										// smoothing
			aggregateLayout.setNodeInfluence(0.8, -2);
			aggregateLayout.setSmoothing(1);
			aggregateLayout.setDoMorph(true);
			aggregateLayout.considerBlockingItems = true;
			aggregateLayout.doMerge = true;
		} else {
			// aggregateLayout.setSmoothing(1);
			// aggregateLayout.setSkip(1);
			// aggregateLayout.setMorphBuffer(5);
			//
			aggregateLayout.setPixelGroup(8); // larger pixel group is faster
			aggregateLayout.setSkip(4); // 8); // skip not necessary when using
										// smoothing
			aggregateLayout.setNodeInfluence(1.8, -1);
			aggregateLayout.setDoMorph(true);
			aggregateLayout.setSmoothing(2);
			aggregateLayout.considerBlockingItems = false; // considering
															// blocking items
															// when choosing
															// nearest
															// neighbours is
															// slow
			aggregateLayout.doMerge = false;
		}
	}

	public static void main(String[] argv) {
		JFrame frame = demo();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}

	public static JFrame demo() {
		VisualWorkspace ad = new VisualWorkspace();
		JFrame frame = new JFrame("S I L V E R |  W O R K S P A C E");
		frame.getContentPane().add(ad);
		frame.pack();
		return frame;
	}

	public int[] getTransparentPalette(int[] colors, int alpha) {
		int[] newPalette = new int[colors.length];
		for (int i = 0; i < colors.length; i++)
			newPalette[i] = ColorLib.setAlpha(colors[i], alpha);
		return newPalette;
	}

	@SuppressWarnings("serial")
	private class CustomTooltip extends JToolTip {
		private JTextArea m_label;
		private JPanel m_panel;
		
		
		
		public CustomTooltip() {
			super();
			m_label = new JTextArea();
			m_label.setBackground(this.getBackground());
			m_panel = new JPanel(new BorderLayout());
			m_panel.add(BorderLayout.SOUTH, m_label);
			setLayout(new BorderLayout());
			add(m_panel);
		}

		@Override
		public Dimension getPreferredSize() {
			return m_panel.getPreferredSize();
		}

		@Override
		public void setTipText(String tipText) {
			if (tipText != null && !tipText.isEmpty()) {
				m_label.setText(tipText);
			} else {
				super.setTipText(tipText);
			}
		}
	}
	
} // end of class AggregateDemo

